package ms.jasim.pddl;

import java.util.List;

import ms.jasim.framework.IConfiguration;
import ms.jasim.framework.IDescriptorProvider;
import ms.jasim.framework.JasimFramework;
import ms.jasim.model.Actor;
import ms.jasim.model.Capability;
import ms.jasim.model.Decomposition;
import ms.jasim.model.Dependency;
import ms.jasim.model.Goal;
import ms.jasim.model.GoalRequest;
import ms.jasim.model.JasimModel;
import ms.jasim.model.items.Item;
import ms.jasim.model.items.ItemList;
import ms.jasim.model.items.PropertyDescriptor;

public class JasimIndirectPddlGenerator implements IPddlProblemGenerator {

	protected JasimModel model;
	protected PddlProblem problem;
	protected List<PropertyDescriptor> actorDescriptors;
	protected List<PropertyDescriptor> capabilityDescriptors;
	protected PropertyDescriptor numInstanceDescriptor;
	protected List<PropertyDescriptor> goalDescriptors;
	
	protected String[][] decomposePredicates = {
			{ PddlPredicate.AND_DECOMPOSE2, PddlPredicate.AND_DECOMPOSE3 },
			{ PddlPredicate.OR_DECOMPOSE2, PddlPredicate.AND_DECOMPOSE3 }
		};

	public JasimIndirectPddlGenerator() {
	}
	
	public PddlProblem getPddlProblem(JasimModel model) {
		if (this.problem == null || model != this.model) {
			this.model = model;
			this.problem = generateProblem();
		}
		return this.problem;
	}

	protected PddlProblem generateProblem() {
		PddlProblem pddl = new PddlProblem(model);
		
		if (model.getOptions() != null) {
			Item optItem = model.getOptions().get("PddlOptions");
			
			try {
				if (optItem != null && optItem instanceof ItemList)
					for (Item opt: (ItemList)optItem)
						pddl.facts.add(new PddlPredicate(PddlPredicate.ASSIGN, 
									new PddlPredicate(opt.getName()), 
									opt.getValue(null)));
			} catch (Exception e) {
			}
		}
		
		IDescriptorProvider provider = JasimFramework.getDescriptorProvider(null);
		
		actorDescriptors = provider != null ? provider.getPropertyDescriptors(Actor.class) : null;
		capabilityDescriptors = provider != null ? provider.getPropertyDescriptors(Capability.class) : null;
		numInstanceDescriptor = model.findPropertyDescriptor(Actor.class, PddlPredicate.NUM_INSTANCE);
		goalDescriptors = provider != null ? provider.getPropertyDescriptors(Goal.class) : null;
		
		for (Goal g: model.getGoals())
			generateGoal(pddl, g);
		
		for (Actor a : model.getActors()) {
			int numIns = (Integer) a.resolveProperty(numInstanceDescriptor).getValue(null);
			if (numIns > 0) {
				PddlObject pddlActor = new PddlObject(a.getName(), PddlObjectType.T_ACTOR);
				pddl.objects.add(pddlActor);
				
				// generate dynamic actor's properties
				if (actorDescriptors != null)
					for (PropertyDescriptor d: actorDescriptors)
						if (d.generatable(true)) {
							Item item = a.getProperties().get(d.getName(), model.getDefaultActorProperties(), d);
							if (item != null && item.getValue(null) instanceof Integer && (Integer)item.getValue(null) > 0)
								pddl.facts.add(new PddlPredicate(PddlPredicate.ASSIGN, 
										new PddlPredicate(d.getPddlPredicate(), pddlActor), item.getValue(null)));
						}
				
				generateCapabilities(pddl, a, pddlActor);
				
				// introduce agent and request
				for (int i = 1; i <= numIns; i ++)
					introduceAgent(pddl, a, pddlActor, i);
			}
		}
		
		if (JasimFramework.getConfiguration().getBoolean(IConfiguration.EXPLICIT_DEPENDENCY, false)) {
			for (Actor a: model.getActors()) {
				PddlObject pddlActor = pddl.objects.get(a.getName());
				generateDependencies(pddl, a, pddlActor);	
			}
		}	
		return pddl;
	}

	protected void introduceAgent(PddlProblem pddl, Actor a, PddlObject pddlActor, int instanceIdx) {
		PddlObject pddlAgent = new PddlObject(a.getName(), PddlObjectType.T_AGENT, instanceIdx);
		PddlObject idxObj = createIndexObject(pddl, instanceIdx);
		pddl.objects.add(pddlAgent);
		
		pddl.facts.add(new PddlPredicate(PddlPredicate.AGENT, pddlActor, idxObj, pddlAgent));
		
		// introduce task based on actor's requests
		for (GoalRequest request : a.getRequests())
			if (request.getGoal() != null && (!request.isSharedInstance() || instanceIdx == 1)) {
				// PddlObject pddlGoal = pddl.objects.get(request.getGoal().getName());
				PddlObject pddlGoal = introduceTask(pddl, request.getGoal(), instanceIdx);
				pddl.facts.add(new PddlPredicate(PddlPredicate.REQUESTS, pddlAgent, pddlGoal));
				pddl.goals.add(new PddlPredicate(PddlPredicate.SATISFIED, pddlGoal));
			}
	}

	protected PddlObject createIndexObject(PddlProblem pddl, int instanceIdx) {
		String idxName = "__i"  + instanceIdx;
		PddlObject idxObj = pddl.findObject(PddlObjectType.T_INDEX, idxName, -1);
		if (idxObj == null)
			pddl.objects.add(idxObj = new PddlObject(idxName, PddlObjectType.T_INDEX));
		return idxObj;
	}

	protected PddlObject introduceTask(PddlProblem pddl, Goal goal, int instanceID) {
		PddlObject pddlTask = pddl.findObject(PddlObjectType.T_TASK, goal.getName(), instanceID);
		PddlObject idxObj = createIndexObject(pddl, instanceID);
		
		if (pddlTask == null) {
			PddlObject pddlGoal = pddl.findObject(PddlObjectType.T_GOAL, goal.getName(), -1); 
			pddlTask = new PddlObject(goal.getName(), PddlObjectType.T_TASK, instanceID);
			
			pddl.objects.add(pddlTask);
			pddl.facts.add(new PddlPredicate(PddlPredicate.TASK, pddlGoal, idxObj, pddlTask));
			
			for (Decomposition de: goal.getDecomposition())
				for (Goal g: de.getSubGoals())
					introduceTask(pddl, g, instanceID);
		}
		return pddlTask;
	}

	protected void generateCapabilities(PddlProblem pddl, Actor a, PddlObject pddlActor) {
		for (Capability cap : a.getCapability()) {
			PddlObject pddlGoal = pddl.objects.get(cap.getGoal().getName());
			pddl.facts.add(new PddlPredicate(PddlPredicate.CAN_PROVIDE, pddlActor, pddlGoal));
			
			if (capabilityDescriptors != null)
				for (PropertyDescriptor d: capabilityDescriptors)
					if (d.generatable(true)) {
						Item item = cap.getProperties().get(d.getName(), model.getDefaultCapabilityProperties(), d);
						if (item != null && item.getValue(null) instanceof Integer && (Integer)item.getValue(null) > 0)
							pddl.facts.add(new PddlPredicate(PddlPredicate.ASSIGN, 
									new PddlPredicate(d.getPddlPredicate(), pddlActor, pddlGoal), item.getValue(null)));
					}
		}
	}

	private void generateDependencies(PddlProblem pddl, Actor a, PddlObject pddlActor) {
		for (Dependency de: a.getDependency()) {
			for (Actor ta: de.getTargetActor()) {
				PddlObject pddlTargetActor = pddl.objects.get(ta.getName());
				for (Goal g: de.getGoal()) {
					PddlObject pddlGoal = pddl.objects.get(g.getName());
					pddl.facts.add(new PddlPredicate(PddlPredicate.CAN_DEPEND_ON_GOAL, pddlActor, pddlTargetActor, pddlGoal));
				}
				if (de.getGoal().size() == 0)
					pddl.facts.add(new PddlPredicate(PddlPredicate.CAN_DEPEND_ON, pddlActor, pddlTargetActor));
			}
			/*if (de.getTargetActor() !=  null) {
				PddlObject pddlTargetActor = pddl.objects.get(de.getTargetActor().getName());
				if (de.getGoal() != null) {
					PddlObject pddlGoal = pddl.objects.get(de.getGoal().getName());
					pddl.facts.add(new PddlPredicate(PddlPredicate.CAN_DEPEND_ON_GOAL, pddlActor, pddlTargetActor, pddlGoal));
				}
				else
					pddl.facts.add(new PddlPredicate(PddlPredicate.CAN_DEPEND_ON, pddlActor, pddlTargetActor));
			}*/
		}
	}
	
	protected void generateGoal(PddlProblem pddl, Goal g) {
		PddlObject pddlGoal = new PddlObject(g.getName(), PddlObjectType.T_GOAL);
		pddl.objects.add(pddlGoal);
		
		if (goalDescriptors != null)
			for (PropertyDescriptor d: goalDescriptors)
				if (d.generatable(true)) {
					Item item = g.getProperties().get(d.getName(), model.getDefaultGoalProperties(), d);
					if (item != null && item.getValue(null) instanceof Integer && (Integer)item.getValue(null) > 0)
						pddl.facts.add(new PddlPredicate(PddlPredicate.ASSIGN, 
								new PddlPredicate(d.getPddlPredicate(), pddlGoal), item.getValue(null)));
				}			
		
		for (Decomposition de : g.getDecomposition()) {
			PddlPredicate pred = new PddlPredicate();
			
			int size = de.getSubGoals().size();
			
			if (size >= 2 && size <= 3)
				pred.setFunctor(decomposePredicates[de.getType().getValue()][size - 2]);
			
			pred.getArgs().add(pddlGoal);
			for (Goal sg: de.getSubGoals())
				pred.getArgs().add(sg.getName());
			pddl.facts.add(pred);
		}
	}

	
	@Override
	public void processPostPddlCommand(PddlProblem prop) {
		// TODO Auto-generated method stub
		
	}
	
}